#ifndef _RUN_COMMAND_HPP
#define _RUN_COMMAND_HPP 1
#include <pwd.h>
#include <unistd.h>
#include <wait.h>

#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>

#include "debug.hpp"
template <typename Key, typename Val>
using umap = std::unordered_map<Key, Val>;

constexpr int INF = 0x7fffffff;

enum class Builtin {
  CD,
  ALIAS,
  BG,
  DIRS,
  ENABLE,
  EVAL,
  EXEC,
  EXIT,
  FG,
  HELP,
  JOBS,
  PWD,
  SOURCE,
  TYPE,
  UNALIAS,
  HISTORY,
  SLEEP,
  True,
  False,
};

namespace builtin {
umap<std::string, Builtin> CallTable{
    std::make_pair("cd", Builtin::CD),
    std::make_pair("alias", Builtin::ALIAS),
    std::make_pair("bg", Builtin::BG),
    std::make_pair("dirs", Builtin::DIRS),
    std::make_pair("enable", Builtin::ENABLE),
    std::make_pair("eval", Builtin::EVAL),
    std::make_pair("exec", Builtin::EXEC),
    std::make_pair("exit", Builtin::EXIT),
    std::make_pair("fg", Builtin::FG),
    std::make_pair("help", Builtin::HELP),
    std::make_pair("jobs", Builtin::JOBS),
    std::make_pair("pwd", Builtin::PWD),
    std::make_pair("source", Builtin::SOURCE),
    std::make_pair("type", Builtin::TYPE),
    std::make_pair("unalias", Builtin::UNALIAS),
    std::make_pair("history", Builtin::HISTORY),
    std::make_pair("sleep", Builtin::SLEEP),
    std::make_pair("true", Builtin::True),
    std::make_pair("false", Builtin::False),
};
std::string pwd(int& ret, bool show = true) {
  char* tmp;
  std::cout << (tmp = get_current_dir_name()) << std::endl;
  // free(tmp);
  return std::string(tmp);
}
void cd(std::string const& dir, int& ret, bool show = true) {
  ret = chdir(dir.c_str());
}
void cd(int& ret, bool show = true) {
  static std::string HOME("/home/");
  passwd* pwd;
  pwd = getpwuid(getuid());
  std::string home(HOME + pwd->pw_name);
  return cd(home, ret, show);
}
std::string alias(int& ret, bool show = true) {}
std::string bg(int& ret, bool show = true) {}
std::string dirs(int& ret, bool show = true) {}
std::string enable(int& ret, bool show = true) {}
std::string eval(int& ret, bool show = true) {}
std::string exec(int& ret, bool show = true) {}
void exit(int& ret) { ret = INF; }
std::string fg(int& ret, bool show = true) {}
std::string help(int& ret, bool show = true) {}
std::string jobs(int& ret, bool show = true) {}
std::string source(int& ret, bool show = true) {}
std::string type(int& ret, bool show = true) {}
std::string unalias(int& ret, bool show = true) {}
std::string history(int& ret, bool show = true) {}
std::string True(int& ret, bool show = true) {}
std::string False(int& ret, bool show = true) {}
std::string sleep(unsigned long time, bool show = true) {
  ::sleep(time);
  return "";
}
}  // namespace builtin

int built(std::vector<std::string> const& cmds);
int built(std::vector<std::string> const& cmds, bool fork_model);
int command(std::vector<std::string> const& cmds);
int command(std::string const& cmds);
int command(std::string const& cmds, bool fork_model);
/**
 * @brief split the command divided by space
 * @test pass
 */
std::vector<std::string> splitCmd(std::string const& str) noexcept {
  std::stringstream ss(str);
  std::vector<std::string> vstr;
  std::string s;
  while (ss >> s) {
    vstr.push_back(s);
  }
  // ss.setstate(ss.goodbit);
  return vstr;
}
/**
 * @brief not support complex command now like && and ||
 * @parameter directly use system call directyly
 */

int CommandRun(std::string const& cmd, bool directly = false) {
  const std::vector<std::string>& cmds = splitCmd(cmd);
  if (cmds.size() == 0) return 0;
  try {
    Builtin cmd = builtin::CallTable.at(cmds[0]);
#ifdef DEBUG
    puts("enter built");
#endif
    return built(cmds, true);
  } catch (const std::exception&) {  // excutable not builtin
#ifdef DEBUG
    puts("enter command");
#endif
    return directly ? command(cmd) : command(cmds);
  }
  // return runCmd();
}
/**
 * @brief For builtin
 * fork and doit in the child process invoking self-deined function
 */
int built(std::vector<std::string> const& cmds) {
  int ret;
#ifdef DEBUG
  puts("In builtin");
#endif
  switch (builtin::CallTable[cmds[0]]) {
    case Builtin::CD:
      if (cmds.size() == 1) {
        builtin::cd(ret);
        break;
      }
      builtin::cd(cmds[1], ret);
      break;
    case Builtin::ALIAS:
      builtin::alias(ret);
      break;
    case Builtin::BG:
      builtin::bg(ret);
      break;
    case Builtin::DIRS:
      builtin::dirs(ret);
      break;
    case Builtin::ENABLE:
      builtin::enable(ret);
      break;
    case Builtin::EVAL:
      builtin::eval(ret);
      break;
    case Builtin::EXEC:
      builtin::exec(ret);
      break;
    case Builtin::EXIT:
#ifdef DEBUG
      puts("In exit");
#endif
      builtin::exit(ret);
#ifdef DEBUG
      puts("Out exit");
#endif
      break;
    case Builtin::FG:
      builtin::fg(ret);
      break;
    case Builtin::HELP:
      builtin::help(ret);
      break;
    case Builtin::JOBS:
      builtin::jobs(ret);
      break;
    case Builtin::PWD:
      builtin::pwd(ret);
      break;
    case Builtin::SOURCE:
      builtin::source(ret);
      break;
    case Builtin::TYPE:
      builtin::type(ret);
      break;
    case Builtin::UNALIAS:
      builtin::unalias(ret);
      break;
    case Builtin::HISTORY:
      builtin::history(ret);
      break;
    case Builtin::True:
      builtin::True(ret);
      break;
    case Builtin::False:
      builtin::False(ret);
      break;
    case Builtin::SLEEP:
      builtin::sleep(std::stoul(cmds[1]));
      ret = 0;
      break;
  }
#ifdef DEBUG
  puts("Out builtin");
  std::cout << "ret:" << ret << std::endl;
#endif
  return ret;
}
int built(std::vector<std::string> const& cmds, bool fork_model) {
  if (!fork_model)
    return built(cmds);
  else {
    int stat;
    pid_t child;
    switch (child = fork()) {
      case -1:  // error
        return 255;
      case 0:  // child
        deputs("buitin fork of child");
        return built(cmds);
      default:              // parent
        setpgid(child, 0);  // to a new process group
        tcsetpgrp(STDIN_FILENO, child);
        // wait(&stat);
        waitpid(-child, &stat, 0);
    }
    return stat;
  }
}
int command(std::vector<std::string> const& cmds) {
  int stat;
  pid_t child;
  switch (child = fork()) {
    case -1:
      return -1;
    case 0: {
      const char* params[cmds.size()];
      for (int i = 0; i < cmds.size(); ++i) {
        params[i] = cmds[i].c_str();
      }
      return execvp(cmds[0].c_str(), (char* const*)params);
    }
    default: {
      setpgid(child, 0);  // divide the child process to a individual group
      wait(&stat);
    }
  }
  return stat;
}
int command(std::string const& cmds, bool fork_model) {
  if (!fork_model)
    return command(cmds);
  else {
    int stat;
    pid_t child;
    switch (child = fork()) {
      case -1:  // error
        return 255;
      case 0:  // child
        deputs("buitin fork of child");
        return command(cmds);
      default:              // parent
        setpgid(child, 0);  // to a new process group
        tcsetpgrp(STDIN_FILENO, child);
        // wait(&stat);
        waitpid(-child, &stat, 0);
    }
    return stat;
  }
}
/**
 * @brief system call simply
 */
int command(std::string const& cmds) { return system(cmds.c_str()); }
#endif  // RUN_COMMAND_HPP